package com.xuecheng.content.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xuecheng.api.content.model.dto.CourseBaseDTO;
import com.xuecheng.api.content.model.dto.TeachplanDTO;
import com.xuecheng.api.content.model.dto.TeachplanMediaDTO;
import com.xuecheng.api.media.model.dto.MediaDTO;
import com.xuecheng.common.domain.code.CommonErrorCode;
import com.xuecheng.common.domain.response.RestResponse;
import com.xuecheng.common.enums.common.AuditEnum;
import com.xuecheng.common.enums.common.CommonEnum;
import com.xuecheng.common.enums.content.CourseAuditEnum;
import com.xuecheng.common.enums.content.CourseModeEnum;
import com.xuecheng.common.enums.content.TeachPlanEnum;
import com.xuecheng.common.exception.ExceptionCast;
import com.xuecheng.content.common.constant.ContentErrorCode;
import com.xuecheng.content.convert.TeachplanConverrt;
import com.xuecheng.content.convert.TeachplanMediaConvert;
import com.xuecheng.content.entity.CourseBase;
import com.xuecheng.content.entity.Teachplan;
import com.xuecheng.content.entity.TeachplanMedia;
import com.xuecheng.content.entity.ext.TeachplanNode;
import com.xuecheng.content.mapper.TeachplanMapper;
import com.xuecheng.content.service.CourseBaseService;
import com.xuecheng.content.service.TeachplanMediaService;
import com.xuecheng.content.service.TeachplanService;
import com.xuecheng.feign.media.MediaApiAgent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 课程计划 服务实现类
 * </p>
 *
 * @author itcast
 */
@Slf4j
@Service
public class TeachplanServiceImpl extends ServiceImpl<TeachplanMapper, Teachplan> implements TeachplanService {

    @Autowired
    private CourseBaseService courseBaseService;

    @Autowired
    private TeachplanMediaService teachplanMediaService;

    @Autowired
    private MediaApiAgent mediaApiAgent;

    /*
    * 业务分析:
    *   1.判断关键数据
    *       courseId  companyId
    *   2.判断业务数据
    *       课程基础信息
    *           判断是否存在
    *           判断是否是同一家教学机构
    *           判断是否删除
    *   3.根据courseidc查询课程计划数据
    *   4.通过java的递归生成课程计划树形结构
    *   5.将数据转为dto并返回
    * */
    public TeachplanDTO getTreeNodes(Long courseId, Long companyId) {

        //业务分析:
        //  1.判断关键数据
        //      courseId  companyId
        if(ObjectUtils.isEmpty(courseId) || ObjectUtils.isEmpty(companyId)){
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }


        //  2.判断业务数据
        //      课程基础信息
        //          判断是否存在
        //          判断是否是同一家教学机构
        //          判断是否删除
        LambdaQueryWrapper<CourseBase> baseLambdaQueryWrapper = new LambdaQueryWrapper<>();
        baseLambdaQueryWrapper.eq(CourseBase::getId,courseId);
        baseLambdaQueryWrapper.eq(CourseBase::getCompanyId,companyId);

        CourseBase courseBase = courseBaseService.getOne(baseLambdaQueryWrapper);

        if(ObjectUtils.isEmpty(courseBase)){
            ExceptionCast.cast(ContentErrorCode.E_120013);
        }

        Integer status = courseBase.getStatus();
        if(!(CommonEnum.USING_FLAG.getCodeInt().equals(status))){
            ExceptionCast.cast(ContentErrorCode.E_120021);
        }


        //  3.根据courseidc查询课程计划数据
        TeachplanMapper baseMapper = this.getBaseMapper();
        List<TeachplanNode> nodes = baseMapper.selectTreeNodesByCourseId(courseId);


        //  4.通过java的递归生成课程计划树形结构
        TeachplanDTO teachplanDTO=null;
        if(CollectionUtils.isEmpty(nodes)){
            teachplanDTO=new TeachplanDTO();
        }else {
            TeachplanNode rootNode = nodes.remove(0);

            generateTreeNodes(rootNode,nodes);

            teachplanDTO=TeachplanConverrt.INSTANCE.node2dto(rootNode);
        }


        //  5.将数据转为dto并返回


        return teachplanDTO;

    }



    /*
    * 业务分析：
    *   主体方法：
    *       1.判断业务操作
    *           判断是创建还是修改：teachplanId
    *           如果有：修改
    *           如果没有：创建
    *       2.执行创建和修改操作并返回dto数据
    *
    *       3.返回结果数据dto
    *
    * */
    @Transactional
    public TeachplanDTO createOrModifyTeachplan(TeachplanDTO dto, Long companyId) {

        // 1.判断业务操作
        //   teachplandto中是否id值
        Long teachPlanId = dto.getTeachPlanId();

        TeachplanDTO resultDTO = null;

        if (ObjectUtils.isEmpty(teachPlanId)) {
            //     如果没有：添加
            resultDTO = createTeachplan(dto,companyId);
        } else {
            //     如果有：修改
            resultDTO = modifyTeachplan(dto,companyId);
        }

        // 3.业务操作后返回结果内容
        return resultDTO;


    }

    /*
     * 业务分析：
     *   1.判断关键数据
     *      courseBaseId  companyId
     *   2.判断业务数据
     *      课程基础信息
     *         判断是否存在
     *         判断是否是同一家机构
     *         判断课程是否删除
     *   3.获得课程下的所有课程计划
     *   4.生成树形结构返回给前端
     * */
    public TeachplanDTO queryTreenodesByCourseId(Long courseBaseId, Long companyId) {
        //1.判断关键数据
        //    courseBaseId  companyId
        // 2.判断业务数据
        //    课程基础信息
        //       判断是否存在
        //       判断是否是同一家机构
        //       判断课程是否删除
        courseBaseService.getCourseById(courseBaseId,companyId);


        // 3.获得课程下的所有课程计划
        // 获得mapper对象
        TeachplanMapper teachplanMapper = this.getBaseMapper();

        List<TeachplanNode> nodes = teachplanMapper.selectTreeNodesByCourseId(courseBaseId);

        TeachplanDTO resultDTO = null;

        if (CollectionUtils.isEmpty(nodes)) {

            resultDTO = new TeachplanDTO();
        } else {

            // 获得一级课程计划，并将其在集合删除，此时集合中只有二级和三级课程计划
            TeachplanNode rootNode = nodes.remove(0);


            // 通过java逻辑代码来生成树形结构
            generateTreeNodes(rootNode,nodes);

            // 4.生成树形结构返回给前端
            resultDTO = TeachplanConverrt.INSTANCE.node2dto(rootNode);
        }


        return resultDTO;
    }

    /*
    * 业务分析：
    *   0.是否开启事务：开启
    *   1.判断关键数据
    *       teachplanId ,mediaID
    *   2.判断业务数据
    *       课程的计划
    *           判断是否是第三级
    *           判断课程类型是否录播（课程基本信息中获得判断）
    *
    *       课程基本信息
    *           判断是否存在是否是同一家机构
    *           判断是否删除
    *           判断审核状态：未提交、审核未通过
    *
    *       媒资信息
    *           判断媒资信息是否存在
    *           判断是否是同一家机构
    *           判断媒资审核状态
    *
    *   3.保存课程计划和媒资信息的数据
    *       判断绑定的信息是否存在
    *           如果存在
    *               修改绑定媒资的信息集合：meidiaID  meidiafilename
    *           如果不存在
    *               添加课程计划媒资信息
    *
    *   4.修改课程计划的类型
    *       mediatype值
    *
    *   5.判断保存的结果并返回数据库最新内存
    *
    * */
    @Transactional
    public TeachplanMediaDTO associationMedia(TeachplanMediaDTO dto, Long companyId) {

        //1.判断关键数据
        //    teachplanId ,mediaID
        if(ObjectUtils.isEmpty(dto.getTeachplanId()) || ObjectUtils.isEmpty(dto.getMediaId()) || ObjectUtils.isEmpty(companyId)){
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }

        //2.判断业务数据
        //    课程的计划
        //判断是否存在
        Teachplan teachplan = this.getById(dto.getTeachplanId());
        if(ObjectUtils.isEmpty(teachplan)){
            ExceptionCast.cast(ContentErrorCode.E_120402);
        }

        //判断课程计划等级：只有第三级课程计划才可以绑定媒资
        if(!(TeachPlanEnum.THIRD_LEVEL.equals(teachplan.getGrade()))){
            ExceptionCast.cast(ContentErrorCode.E_120410);
        }


        //    课程基本信息
        //        判断是否存在是否是同一家机构
        //        判断是否删除
        //        判断审核状态：未提交、审核未通过
        CourseBaseDTO courseBase = getCourseAndVerify(companyId, teachplan.getCourseId());
        //判断课程类型：只有是点播课程才可以绑定媒资信息
        String teachmode = courseBase.getTeachmode();
        if(!(CourseModeEnum.COURSE_MODE_RECORD_STATUS.getCode().equals(teachmode))){
            ExceptionCast.cast(ContentErrorCode.E_120418);
        }


        //    媒资信息
        //        判断媒资信息是否存在
        //        判断是否是同一家机构
        //        判断媒资审核状态
        RestResponse<MediaDTO> mediaResponse = mediaApiAgent.getMediaById4s(dto.getMediaId());

        if(!(mediaResponse.isSuccessful())){
            ExceptionCast.castWithCodeAndDesc(mediaResponse.getCode(),mediaResponse.getMsg());
        }

        MediaDTO mediaDTO = mediaResponse.getResult();
        if(!(ObjectUtils.nullSafeEquals(companyId,mediaDTO.getCompanyId()))){
            ExceptionCast.cast(CommonErrorCode.E_100108);
        }

        if(!(AuditEnum.AUDIT_PASTED_STATUS.getCode().equals(mediaDTO.getAuditStatus()))){
            ExceptionCast.cast(ContentErrorCode.E_120416);
        }


        //3.保存课程计划和媒资信息的数据
        //    判断绑定的信息是否存在
        LambdaQueryWrapper<TeachplanMedia> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TeachplanMedia::getTeachplanId,dto.getTeachplanId());
        queryWrapper.eq(TeachplanMedia::getCourseId,dto.getCourseId());

        TeachplanMedia teachplanMedia = teachplanMediaService.getOne(queryWrapper);

        boolean teachplanMeidaResult=false;
        if(ObjectUtils.isEmpty(teachplanMedia)){
            //        如果不存在
            //            添加课程计划媒资信息
            teachplanMedia = TeachplanMedia.builder()
                    .mediaId(dto.getMediaId())
                    .mediaFilename(mediaDTO.getFilename())
                    .teachplanId(dto.getTeachplanId())
                    .courseId(teachplan.getCourseId())
                    .build();

            teachplanMeidaResult=teachplanMediaService.save(teachplanMedia);

        }else {
            //        如果存在
            //            修改绑定媒资的信息集合：meidiaID  meidiafilename
            LambdaUpdateWrapper<TeachplanMedia> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(TeachplanMedia::getMediaId,mediaDTO.getId());
            updateWrapper.set(TeachplanMedia::getMediaFilename,mediaDTO.getFilename());
            updateWrapper.eq(TeachplanMedia::getId,teachplanMedia.getId());

            teachplanMeidaResult=teachplanMediaService.update(updateWrapper);

        }

        if(!(teachplanMeidaResult)){
            ExceptionCast.cast(ContentErrorCode.E_120421);
        }


        //4.修改课程计划的类型
        //    mediatype值
        changeTeachplanMediaType(teachplan.getId(),mediaDTO.getType());

        //5.判断保存的结果并返回数据库最新内存
        TeachplanMedia po = teachplanMediaService.getById(teachplanMedia.getId());
        TeachplanMediaDTO resultDTO = TeachplanMediaConvert.INSTANCE.entity2dto(po);

        return resultDTO;
    }

    private void changeTeachplanMediaType(Long teachplanId, String mediaType) {
        LambdaUpdateWrapper<Teachplan> teachplanUpdateWrapper = new LambdaUpdateWrapper<>();
        teachplanUpdateWrapper.set(Teachplan::getMediaType,mediaType);
        teachplanUpdateWrapper.set(Teachplan::getChangeDate, LocalDateTime.now());
        teachplanUpdateWrapper.eq(Teachplan::getId,teachplanId);

        boolean teachplanResult = this.update(teachplanUpdateWrapper);
        if(!teachplanResult){
            ExceptionCast.cast(ContentErrorCode.E_120421);
        }


    }

    private TeachplanDTO modifyTeachplan(TeachplanDTO dto, Long companyId) {
        return null;
    }

    /*
     * 业务分析：
     *   1.判断关键数据
     *     pname  grade  courseid
     *   2.判断业务数据
     *     课程基础信息
     *        判断是否存在
     *        判断是否是同一家机构
     *        判断是否删除
     *        判断审核状态（未提交、审核未通过）
     *
     *   3.获得父级数据
     *     添加课程计划中要依据父级数据进行赋值
     *        添加课程计划的parentid = 父级数据id
     *        添加课程计划的grade = 父级数据grade+1
     *        添加课程计划的orderby = 父级数据所有子级数据的个数+1
     *
     *   4.保存课程计划数据并判断保存后的结果
     *
     * */
    private TeachplanDTO createTeachplan(TeachplanDTO dto, Long companyId) {
        //1.判断关键数据
        if (StringUtils.isEmpty(dto.getPname())||
                ObjectUtils.isEmpty(dto.getGrade())||
                ObjectUtils.isEmpty(dto.getCourseId())
        ) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }


        // 2.判断业务数据
        //   课程基础信息
        //      判断是否存在
        //      判断是否是同一家机构
        //      判断是否删除
        //      判断审核状态（未提交、审核未通过）
        CourseBaseDTO courseBase = getCourseAndVerify(companyId, dto.getCourseId());



        // 3.获得父级数据
        //   添加课程计划中要依据父级数据进行赋值
        //      添加课程计划的parentid = 父级数据id
        //      添加课程计划的grade = 父级数据grade+1
        //      添加课程计划的orderby = 父级数据所有子级数据的个数+1
        Teachplan parentNode =  getParentNode(courseBase,dto);

        dto.setGrade(parentNode.getGrade()+1);
        dto.setParentid(parentNode.getParentid());

        // select count(*) as ncount from teachplan where parentId = ?
        LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Teachplan::getParentid,parentNode.getId());

        int count = this.count(queryWrapper);
        dto.setOrderby(count+1);


        // 4.保存课程计划数据并判断保存后的结果
        Teachplan teachplan = TeachplanConverrt.INSTANCE.dto2entity(dto);

        boolean result = this.save(teachplan);

        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120407);
        }

        Teachplan po = this.getById(teachplan.getId());

        TeachplanDTO resultDTO = TeachplanConverrt.INSTANCE.entity2dto(po);

        return resultDTO;

    }

    /*
    * 业务分析：
    *   1.判断添加的课程计划是几级数据
    *       前端只能操作：2级和3级
    *       区分：三级课程计划添加是需要携带parentid
    *   2.获得父级数据
    *       获得二级
    *           如果获得父级数据不存在：自动在后端创建
    *           如果获得父级数据存在：直接返回
    *       获得三级：
    *           如果获得父级数据不存在，直接抛出异常
    *           如果获得父级数据存在：直接返回
    *
    * */
    private Teachplan getParentNode(CourseBaseDTO courseBase, TeachplanDTO dto) {

        //1.判断添加的课程计划是几级数据
        //    前端只能操作：2级和3级
        //    区分：三级课程计划添加是需要携带parentid
        if(ObjectUtils.isEmpty(dto.getParentid())){
            //2.获得父级数据
            //    获得二级
            //        如果获得父级数据不存在：自动在后端创建
            //        如果获得父级数据存在：直接返回
            //select * from teachplam where courseid = ? and parentid =0 and grade =1
            LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teachplan::getParentid,TeachPlanEnum.FIRST_PARENTID_FLAG);
            queryWrapper.eq(Teachplan::getCourseId,courseBase.getCourseBaseId());

            Teachplan rootNode = this.getOne(queryWrapper);

            if(ObjectUtils.isEmpty(rootNode)){
                rootNode=new Teachplan();
                rootNode.setPname(courseBase.getName());
                rootNode.setParentid(new Long(TeachPlanEnum.FIRST_PARENTID_FLAG));
                rootNode.setGrade(TeachPlanEnum.FIRST_LEVEL);
                rootNode.setOrderby(TeachPlanEnum.FIRST_LEVEL);
                rootNode.setCourseId(courseBase.getCourseBaseId());

                boolean result = this.save(rootNode);
                if(!result){
                    ExceptionCast.cast(ContentErrorCode.E_120107);
                }
            }
            return rootNode;
        }else {
            //    获得三级：
            //        如果获得父级数据不存在，直接抛出异常
            //        如果获得父级数据存在：直接返回
            //select * from teachplan parentid = ?
            LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teachplan::getId,dto.getParentid());

            Teachplan parentNode = this.getOne(queryWrapper);

            if(ObjectUtils.isEmpty(parentNode)){
                ExceptionCast.cast(ContentErrorCode.E_120408);
            }

            return parentNode;

        }

    }

    

    private void generateTreeNodes(TeachplanNode parentNode, List<TeachplanNode> nodes) {

        //1.判断父级集合是否为空，若果为空需要创建
        if(CollectionUtils.isEmpty(parentNode.getChildrenNodes())){
            parentNode.setChildrenNodes(new ArrayList<>(8));
        }

        //2.循环遍历集合生成树形结构
        for (TeachplanNode node : nodes) {

            //2.1判断node是否和parentnode有关联，如果有关联将node存放在parentNode集合中
            if(parentNode.getId().equals(node.getParentid())){

                parentNode.getChildrenNodes().add(node);

                // 将子级数据作为父级再次循环便利（递归）
                // 如果子级数据不是第三级需要进行递归
                if(!(TeachPlanEnum.THIRD_LEVEL.equals(node.getGrade()))){
                    generateTreeNodes(node,nodes);
                }else {
                    // 如果课程计划是第三级，需要查询是否有管理的媒资信息，如果有，需要关联
                    LambdaQueryWrapper<TeachplanMedia> queryWrapper = new LambdaQueryWrapper<>();
                    queryWrapper.eq(TeachplanMedia::getTeachplanId, node.getId());

                    TeachplanMedia teachplanMedia = teachplanMediaService.getOne(queryWrapper);
                    if (!(ObjectUtils.isEmpty(teachplanMedia))) {
                        node.setTeachplanMedia(teachplanMedia);
                    }
                }
            }
        }

    }


    private CourseBaseDTO getCourseAndVerify(Long companyId, Long courseId) {
        //     课程基础信息
        //         判断是否存在
        //         判断是否同一家机构
        //         判断是否删除
        //         判断课程基础信息审核状态：未提交和审核未通过
        CourseBaseDTO courseBase = courseBaseService.getCourseById(courseId, companyId);

        String auditStatus = courseBase.getAuditStatus();
        if (CourseAuditEnum.AUDIT_PASTED_STATUS.getCode().equals(auditStatus)||
                CourseAuditEnum.AUDIT_COMMIT_STATUS.getCode().equals(auditStatus)||
                CourseAuditEnum.AUDIT_PUBLISHED_STATUS.getCode().equals(auditStatus)
        ) {
            ExceptionCast.cast(ContentErrorCode.E_120015);
        }
        return courseBase;
    }
}
